<!DOCTYPE html>
<html lang="zh-CN">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/blog/img/favicon.png">
  <link rel="icon" type="image/png" href="/blog/img/favicon.png">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
    <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  
  <meta name="theme-color" content="#35495e">
  <meta name="description" content="小菜头的博客，主要记录学习笔记，包含：html,css,javascript,jQuery,vue等">
  <meta name="author" content="A-cai">
  <meta name="keywords" content="html,css,javascript,jQuery,vue">
  <title>说说浏览器和Node事件循环的区别 - A-cai的blog</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/blog/lib/hint/hint.min.css" />

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.0.0/styles/atom-one-dark.min.css" />
  

  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_yg9cfy8wd6.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_pjno9b9zyxs.css">


<link  rel="stylesheet" href="/blog/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script  src="/blog/js/utils.js" ></script>
<meta name="generator" content="Hexo 4.2.0"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/blog/">&nbsp;<strong>A-cai的学习笔记</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner intro-2" id="background" parallax=true
         style="background: url('/blog/img/default.png') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="container text-center white-text fade-in-up">
            <span class="h2" id="subtitle">
              
            </span>

            
              
  <div class="mt-3 post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2020-06-26 14:04">
      2020年6月26日 下午
    </time>
  </div>


<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      4.5k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      51
       分钟
    </span>
  

  
  
    
      <!-- 不蒜子统计文章PV -->
      <span id="busuanzi_container_page_pv" style="display: none">
        <i class="iconfont icon-eye" aria-hidden="true"></i>
        <span id="busuanzi_value_page_pv"></span> 次
      </span>
    
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <div class="post-content mx-auto" id="post">
            
              <p class="note note-info">
                
                  本文最后更新于：2020年4月1日 下午
                
              </p>
            
            <article class="markdown-body">
              <h1 id="浏览器与Node的事件循环有何区别"><a href="#浏览器与Node的事件循环有何区别" class="headerlink" title="浏览器与Node的事件循环有何区别?"></a>浏览器与Node的事件循环有何区别?</h1><p>(Event Loop)</p>
<h1 id="浏览器"><a href="#浏览器" class="headerlink" title="浏览器"></a>浏览器</h1><p><strong>关于微任务和宏任务在浏览器的执行顺序是这样的：</strong></p>
<ul>
<li>执行一只task（宏任务）</li>
<li>执行完micro-task队列 （微任务）</li>
</ul>
<p>如此循环往复下去</p>
<blockquote>
<p>浏览器的task（宏任务）执行顺序在 <a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loops" target="_blank" rel="noopener">html#event-loops</a> 里面有讲就不翻译了 常见的 task（宏任务） 比如：setTimeout、setInterval、script（整体代码）、 I/O 操作、UI 渲染等。 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性) 等。</p>
</blockquote>
<h1 id="Node"><a href="#Node" class="headerlink" title="Node"></a>Node</h1><p>Node的事件循环是libuv实现的，引用一张官网的图：</p>
<p><img src="https://user-images.githubusercontent.com/20101525/53734427-eba9e880-3ebe-11e9-8511-eb4948e336ae.png" srcset="/blog/img/loading.gif" alt="default"></p>
<p>大体的task（宏任务）执行顺序是这样的：</p>
<ul>
<li>timers定时器：本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数。</li>
<li>pending callbacks待定回调：执行延迟到下一个循环迭代的 I/O 回调。</li>
<li>idle, prepare：仅系统内部使用。</li>
<li>poll 轮询：检索新的 I/O 事件;执行与 I/O 相关的回调（几乎所有情况下，除了关闭的回调函数，它们由计时器和 setImmediate() 排定的之外），其余情况 node 将在此处阻塞。</li>
<li>check 检测：setImmediate() 回调函数在这里执行。</li>
<li>close callbacks 关闭的回调函数：一些准备关闭的回调函数，如：socket.on(‘close’, …)。</li>
</ul>
<p><strong>微任务和宏任务在Node的执行顺序</strong></p>
<p>Node 10以前：</p>
<ul>
<li>执行完一个阶段的所有任务</li>
<li>执行完nextTick队列里面的内容</li>
<li>然后执行完微任务队列的内容</li>
</ul>
<p>Node 11以后： 和浏览器的行为统一了，都是每执行一个宏任务就执行完微任务队列。</p>
<h2 id="一、线程与进程"><a href="#一、线程与进程" class="headerlink" title="一、线程与进程"></a>一、线程与进程</h2><h3 id="1-概念"><a href="#1-概念" class="headerlink" title="1.概念"></a>1.概念</h3><p>我们经常说JS 是单线程执行的，指的是一个进程里只有一个主线程，那到底什么是线程？什么是进程？</p>
<p>官方的说法是：<strong>进程是 CPU资源分配的最小单位；线程是 CPU调度的最小单位</strong>。这两句话并不好理解，我们先来看张图：</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/9/168333c14c85d794?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<ul>
<li>进程好比图中的工厂，有单独的专属自己的工厂资源。</li>
<li>线程好比图中的工人，多个工人在一个工厂中协作工作，工厂与工人是 1:n的关系。也就是说<strong>一个进程由一个或多个线程组成，线程是一个进程中代码的不同执行路线</strong>；</li>
<li>工厂的空间是工人们共享的，这象征<strong>一个进程的内存空间是共享的，每个线程都可用这些共享内存</strong>。</li>
<li>多个工厂之间独立存在。</li>
</ul>
<h3 id="2-多进程与多线程"><a href="#2-多进程与多线程" class="headerlink" title="2.多进程与多线程"></a>2.多进程与多线程</h3><ul>
<li>多进程：在同一个时间里，同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的，比如你可以听歌的同时，打开编辑器敲代码，编辑器和听歌软件的进程之间丝毫不会相互干扰。</li>
<li>多线程：程序中包含多个执行流，即在一个程序中可以同时运行多个不同的线程来执行不同的任务，也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。</li>
</ul>
<p>以Chrome浏览器中为例，当你打开一个 Tab 页时，其实就是创建了一个进程，一个进程中可以有多个线程（下文会详细介绍），比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时，其实就是创建了一个线程，当请求结束后，该线程可能就会被销毁。</p>
<h2 id="二、浏览器内核"><a href="#二、浏览器内核" class="headerlink" title="二、浏览器内核"></a>二、浏览器内核</h2><p>简单来说浏览器内核是通过取得页面内容、整理信息（应用CSS）、计算和组合最终输出可视化的图像结果，通常也被称为渲染引擎。</p>
<p>浏览器内核是多线程，在内核控制下各线程相互配合以保持同步，一个浏览器通常由以下常驻线程组成：</p>
<ul>
<li>GUI 渲染线程</li>
<li>JavaScript引擎线程</li>
<li>定时触发器线程</li>
<li>事件触发线程</li>
<li>异步http请求线程</li>
</ul>
<h3 id="1-GUI渲染线程"><a href="#1-GUI渲染线程" class="headerlink" title="1.GUI渲染线程"></a>1.GUI渲染线程</h3><ul>
<li>主要负责页面的渲染，解析HTML、CSS，构建DOM树，布局和绘制等。</li>
<li>当界面需要重绘或者由于某种操作引发回流时，将执行该线程。</li>
<li>该线程与JS引擎线程互斥，当执行JS引擎线程时，GUI渲染会被挂起，当任务队列空闲时，主线程才会去执行GUI渲染。</li>
</ul>
<h3 id="2-JS引擎线程"><a href="#2-JS引擎线程" class="headerlink" title="2.JS引擎线程"></a>2.JS引擎线程</h3><ul>
<li>该线程当然是主要负责处理 JavaScript脚本，执行代码。</li>
<li>也是主要负责执行准备好待执行的事件，即定时器计数结束，或者异步请求成功并正确返回时，将依次进入任务队列，等待 JS引擎线程的执行。</li>
<li>当然，该线程与 GUI渲染线程互斥，当 JS引擎线程执行 JavaScript脚本时间过长，将导致页面渲染的阻塞。</li>
</ul>
<h3 id="3-定时器触发线程"><a href="#3-定时器触发线程" class="headerlink" title="3.定时器触发线程"></a>3.定时器触发线程</h3><ul>
<li>负责执行异步定时器一类的函数的线程，如： setTimeout，setInterval。</li>
<li>主线程依次执行代码时，遇到定时器，会将定时器交给该线程处理，当计数完毕后，事件触发线程会将计数完毕后的事件加入到任务队列的尾部，等待JS引擎线程执行。</li>
</ul>
<h3 id="4-事件触发线程"><a href="#4-事件触发线程" class="headerlink" title="4.事件触发线程"></a>4.事件触发线程</h3><ul>
<li>主要负责将准备好的事件交给 JS引擎线程执行。</li>
</ul>
<p>比如 setTimeout定时器计数结束， ajax等异步请求成功并触发回调函数，或者用户触发点击事件时，该线程会将整装待发的事件依次加入到任务队列的队尾，等待 JS引擎线程的执行。</p>
<h3 id="5-异步http请求线程"><a href="#5-异步http请求线程" class="headerlink" title="5.异步http请求线程"></a>5.异步http请求线程</h3><ul>
<li>负责执行异步请求一类的函数的线程，如： Promise，axios，ajax等。</li>
<li>主线程依次执行代码时，遇到异步请求，会将函数交给该线程处理，当监听到状态码变更，如果有回调函数，事件触发线程会将回调函数加入到任务队列的尾部，等待JS引擎线程执行。</li>
</ul>
<h2 id="三、浏览器中的-Event-Loop"><a href="#三、浏览器中的-Event-Loop" class="headerlink" title="三、浏览器中的 Event Loop"></a>三、浏览器中的 Event Loop</h2><h3 id="1-Micro-Task-与-Macro-Task"><a href="#1-Micro-Task-与-Macro-Task" class="headerlink" title="1.Micro-Task 与 Macro-Task"></a>1.Micro-Task 与 Macro-Task</h3><p>浏览器端事件循环中的异步队列有两种：macro（宏任务）队列和 micro（微任务）队列。</p>
<ul>
<li>常见的 macro-task 比如：setTimeout、setInterval、script（整体代码）、 I/O 操作、UI 渲染等。</li>
<li>常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性) 等。</li>
</ul>
<h3 id="2-Event-Loop-过程解析"><a href="#2-Event-Loop-过程解析" class="headerlink" title="2.Event Loop 过程解析"></a>2.Event Loop 过程解析</h3><p>一个完整的 Event Loop 过程，可以概括为以下阶段：</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/10/1683863633586974?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<ul>
<li>一开始执行栈空,我们可以把<strong>执行栈认为是一个存储函数调用的栈结构，遵循先进后出的原则</strong>。micro 队列空，macro 队列里有且只有一个 script 脚本（整体代码）。</li>
<li>全局上下文（script 标签）被推入执行栈，同步代码执行。在执行的过程中，会判断是同步任务还是异步任务，通过对一些接口的调用，可以产生新的 macro-task 与 micro-task，它们会分别被推入各自的任务队列里。同步代码执行完了，script 脚本会被移出 macro 队列，这个过程本质上是队列的 macro-task 的执行和出队的过程。</li>
<li>上一步我们出队的是一个 macro-task，这一步我们处理的是 micro-task。但需要注意的是：当 macro-task 出队时，任务是<strong>一个一个</strong>执行的；而 micro-task 出队时，任务是<strong>一队一队</strong>执行的。因此，我们处理 micro 队列这一步，会逐个执行队列中的任务并把它出队，直到队列被清空。</li>
<li><strong>执行渲染操作，更新界面</strong></li>
<li>检查是否存在 Web worker 任务，如果有，则对其进行处理</li>
<li>上述过程循环往复，直到两个队列都清空</li>
</ul>
<p>我们总结一下，每一次循环都是一个这样的过程：</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/10/1683877ba9aab056?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<p><strong>当某个宏任务执行完后,会查看是否有微任务队列。如果有，先执行微任务队列中的所有任务，如果没有，会读取宏任务队列中排在最前的任务，执行宏任务的过程中，遇到微任务，依次加入微任务队列。栈空后，再次读取微任务队列里的任务，依次类推。</strong></p>
<p>接下来我们看道例子来介绍上面流程：</p>
<div class="hljs"><pre><code class="hljs coffeescript">Promise.resolve().<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Promise1'</span>)  
  setTimeout(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'setTimeout2'</span>)
  &#125;,<span class="hljs-number">0</span>)
&#125;)
setTimeout(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'setTimeout1'</span>)
  Promise.resolve().<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Promise2'</span>)    
  &#125;)
&#125;,<span class="hljs-number">0</span>)
复制代码</code></pre></div>

<p>最后输出结果是Promise1，setTimeout1，Promise2，setTimeout2</p>
<ul>
<li>一开始执行栈的同步任务（这属于宏任务）执行完毕，会去查看是否有微任务队列，上题中存在(有且只有一个)，然后执行微任务队列中的所有任务输出Promise1，同时会生成一个宏任务 setTimeout2</li>
<li>然后去查看宏任务队列，宏任务 setTimeout1 在 setTimeout2 之前，先执行宏任务 setTimeout1，输出 setTimeout1</li>
<li>在执行宏任务setTimeout1时会生成微任务Promise2 ，放入微任务队列中，接着先去清空微任务队列中的所有任务，输出 Promise2</li>
<li>清空完微任务队列中的所有任务后，就又会去宏任务队列取一个，这回执行的是 setTimeout2</li>
</ul>
<h2 id="四、Node-中的-Event-Loop"><a href="#四、Node-中的-Event-Loop" class="headerlink" title="四、Node 中的 Event Loop"></a>四、Node 中的 Event Loop</h2><h3 id="1-Node简介"><a href="#1-Node简介" class="headerlink" title="1.Node简介"></a>1.Node简介</h3><p>Node 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js采用V8作为js的解析引擎，而I/O处理方面使用了自己设计的libuv，libuv是一个基于事件驱动的跨平台抽象层，封装了不同操作系统一些底层特性，对外提供统一的API，事件循环机制也是它里面的实现（下文会详细介绍）。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/11/1683d81674f076eb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<p>Node.js的运行机制如下:</p>
<ul>
<li>V8引擎解析JavaScript脚本。</li>
<li>解析后的代码，调用Node API。</li>
<li>libuv库负责Node API的执行。它将不同的任务分配给不同的线程，形成一个Event Loop（事件循环），以异步的方式将任务的执行结果返回给V8引擎。</li>
<li>V8引擎再将结果返回给用户。</li>
</ul>
<h3 id="2-六个阶段"><a href="#2-六个阶段" class="headerlink" title="2.六个阶段"></a>2.六个阶段</h3><p>其中libuv引擎中的事件循环分为 6 个阶段，它们会按照顺序反复运行。每当进入某一个阶段的时候，都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值，就会进入下一阶段。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/12/16841bd9860c1ee9?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<p>从上图中，大致看出node中的事件循环的顺序：</p>
<p>外部输入数据–&gt;轮询阶段(poll)–&gt;检查阶段(check)–&gt;关闭事件回调阶段(close callback)–&gt;定时器检测阶段(timer)–&gt;I/O事件回调阶段(I/O callbacks)–&gt;闲置阶段(idle, prepare)–&gt;轮询阶段（按照该顺序反复运行）…</p>
<ul>
<li>timers 阶段：这个阶段执行timer（setTimeout、setInterval）的回调</li>
<li>I/O callbacks 阶段：处理一些上一轮循环中的少数未执行的 I/O 回调</li>
<li>idle, prepare 阶段：仅node内部使用</li>
<li>poll 阶段：获取新的I/O事件, 适当的条件下node将阻塞在这里</li>
<li>check 阶段：执行 setImmediate() 的回调</li>
<li>close callbacks 阶段：执行 socket 的 close 事件回调</li>
</ul>
<p>注意：<strong>上面六个阶段都不包括 process.nextTick()</strong>(下文会介绍)</p>
<p>接下去我们详细介绍<code>timers</code>、<code>poll</code>、<code>check</code>这3个阶段，因为日常开发中的绝大部分异步任务都是在这3个阶段处理的。</p>
<h4 id="1-timer"><a href="#1-timer" class="headerlink" title="(1) timer"></a>(1) timer</h4><p>timers 阶段会执行 setTimeout 和 setInterval 回调，并且是由 poll 阶段控制的。 同样，<strong>在 Node 中定时器指定的时间也不是准确时间，只能是尽快执行</strong>。</p>
<h4 id="2-poll"><a href="#2-poll" class="headerlink" title="(2) poll"></a>(2) poll</h4><p>poll 是一个至关重要的阶段，这一阶段中，系统会做两件事情</p>
<p>1.回到 timer 阶段执行回调</p>
<p>2.执行 I/O 回调</p>
<p>并且在进入该阶段时如果没有设定了 timer 的话，会发生以下两件事情</p>
<ul>
<li>如果 poll 队列不为空，会遍历回调队列并同步执行，直到队列为空或者达到系统限制</li>
<li>如果 poll 队列为空时，会有两件事发生<ul>
<li>如果有 setImmediate 回调需要执行，poll 阶段会停止并且进入到 check 阶段执行回调</li>
<li>如果没有 setImmediate 回调需要执行，会等待回调被加入到队列中并立即执行回调，这里同样会有个超时时间设置防止一直等待下去</li>
</ul>
</li>
</ul>
<p>当然设定了 timer 的话且 poll 队列为空，则会判断是否有 timer 超时，如果有的话会回到 timer 阶段执行回调。</p>
<h4 id="3-check阶段"><a href="#3-check阶段" class="headerlink" title="(3) check阶段"></a>(3) check阶段</h4><p>setImmediate()的回调会被加入check队列中，从event loop的阶段图可以知道，check阶段的执行顺序在poll阶段之后。 我们先来看个例子:</p>
<div class="hljs"><pre><code class="hljs javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'start'</span>)
setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timer1'</span>)
  <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise1'</span>)
  &#125;)
&#125;, <span class="hljs-number">0</span>)
setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timer2'</span>)
  <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise2'</span>)
  &#125;)
&#125;, <span class="hljs-number">0</span>)
<span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise3'</span>)
&#125;)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'end'</span>)
<span class="hljs-comment">//start=&gt;end=&gt;promise3=&gt;timer1=&gt;timer2=&gt;promise1=&gt;promise2</span>
复制代码</code></pre></div>

<ul>
<li>一开始执行栈的同步任务（这属于宏任务）执行完毕后（依次打印出start end，并将2个timer依次放入timer队列）,会先去执行微任务（<strong>这点跟浏览器端的一样</strong>），所以打印出promise3</li>
<li>然后进入timers阶段，执行timer1的回调函数，打印timer1，并将promise.then回调放入microtask队列，同样的步骤执行timer2，打印timer2；这点跟浏览器端相差比较大，<strong>timers阶段有几个setTimeout/setInterval都会依次执行</strong>，并不像浏览器端，每执行一个宏任务后就去执行一个微任务（关于Node与浏览器的 Event Loop 差异，下文还会详细介绍）。</li>
</ul>
<h3 id="3-Micro-Task-与-Macro-Task"><a href="#3-Micro-Task-与-Macro-Task" class="headerlink" title="3.Micro-Task 与 Macro-Task"></a>3.Micro-Task 与 Macro-Task</h3><p>Node端事件循环中的异步队列也是这两种：macro（宏任务）队列和 micro（微任务）队列。</p>
<ul>
<li>常见的 macro-task 比如：setTimeout、setInterval、 setImmediate、script（整体代码）、 I/O 操作等。</li>
<li>常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等。</li>
</ul>
<h3 id="4-注意点"><a href="#4-注意点" class="headerlink" title="4.注意点"></a>4.注意点</h3><h4 id="1-setTimeout-和-setImmediate"><a href="#1-setTimeout-和-setImmediate" class="headerlink" title="(1) setTimeout 和 setImmediate"></a>(1) setTimeout 和 setImmediate</h4><p>二者非常相似，区别主要在于调用时机不同。</p>
<ul>
<li>setImmediate 设计在poll阶段完成时执行，即check阶段；</li>
<li>setTimeout 设计在poll阶段为空闲时，且设定时间到达后执行，但它在timer阶段执行</li>
</ul>
<div class="hljs"><pre><code class="hljs javascript">setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">timeout</span> (<span class="hljs-params"></span>) </span>&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timeout'</span>);
&#125;,<span class="hljs-number">0</span>);
setImmediate(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">immediate</span> (<span class="hljs-params"></span>) </span>&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'immediate'</span>);
&#125;);
复制代码</code></pre></div>

<ul>
<li>对于以上代码来说，setTimeout 可能执行在前，也可能执行在后。</li>
<li>首先 setTimeout(fn, 0) === setTimeout(fn, 1)，这是由源码决定的 进入事件循环也是需要成本的，如果在准备时候花费了大于 1ms 的时间，那么在 timer 阶段就会直接执行 setTimeout 回调</li>
<li>如果准备时间花费小于 1ms，那么就是 setImmediate 回调先执行了</li>
</ul>
<p>但当二者在异步i/o callback内部调用时，总是先执行setImmediate，再执行setTimeout</p>
<div class="hljs"><pre><code class="hljs coffeescript">const fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
fs.readFile(__filename, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
    setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timeout'</span>);
    &#125;, <span class="hljs-number">0</span>)
    setImmediate(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'immediate'</span>)
    &#125;)
&#125;)
<span class="hljs-regexp">//</span> immediate
<span class="hljs-regexp">//</span> timeout
复制代码</code></pre></div>

<p>在上述代码中，setImmediate 永远先执行。因为两个代码写在 IO 回调中，IO 回调是在 poll 阶段执行，当回调执行完毕后队列为空，发现存在 setImmediate 回调，所以就直接跳转到 check 阶段去执行回调了。</p>
<h4 id="2-process-nextTick"><a href="#2-process-nextTick" class="headerlink" title="(2) process.nextTick"></a>(2) process.nextTick</h4><p>这个函数其实是独立于 Event Loop 之外的，它有一个自己的队列，当每个阶段完成后，如果存在 nextTick 队列，就会清空队列中的所有回调函数，并且优先于其他 microtask 执行。</p>
<div class="hljs"><pre><code class="hljs coffeescript">setTimeout(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timer1'</span>)
 Promise.resolve().<span class="hljs-keyword">then</span>(function() &#123;
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise1'</span>)
 &#125;)
&#125;, <span class="hljs-number">0</span>)
process.nextTick(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'nextTick'</span>)
 process.nextTick(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'nextTick'</span>)
   process.nextTick(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'nextTick'</span>)
     process.nextTick(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;
       <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'nextTick'</span>)
     &#125;)
   &#125;)
 &#125;)
&#125;)
<span class="hljs-regexp">//</span> nextTick=&gt;nextTick=&gt;nextTick=&gt;nextTick=&gt;timer1=&gt;promise1
复制代码</code></pre></div>

<h2 id="五、Node与浏览器的-Event-Loop-差异"><a href="#五、Node与浏览器的-Event-Loop-差异" class="headerlink" title="五、Node与浏览器的 Event Loop 差异"></a>五、Node与浏览器的 Event Loop 差异</h2><p><strong>浏览器环境下，microtask的任务队列是每个macrotask执行完之后执行。而在Node.js中，microtask会在事件循环的各个阶段之间执行，也就是一个阶段执行完毕，就会去执行microtask队列的任务</strong>。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/12/16841bad1cda741f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" srcset="/blog/img/loading.gif" alt="img"></p>
<p>接下我们通过一个例子来说明两者区别：</p>
<div class="hljs"><pre><code class="hljs javascript">setTimeout(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timer1'</span>)
    <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>&#123;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise1'</span>)
    &#125;)
&#125;, <span class="hljs-number">0</span>)
setTimeout(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>&#123;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'timer2'</span>)
    <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>&#123;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'promise2'</span>)
    &#125;)
&#125;, <span class="hljs-number">0</span>)
复制代码</code></pre></div>

<p>浏览器端运行结果：<code>timer1=&gt;promise1=&gt;timer2=&gt;promise2</code></p>
<p>浏览器端的处理过程如下：</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/12/16841d6392e8f537?imageslim" srcset="/blog/img/loading.gif" alt="img"></p>
<p>Node端运行结果分两种情况：</p>
<ul>
<li>如果是node11版本一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列，这就跟浏览器端运行一致，最后的结果为<code>timer1=&gt;promise1=&gt;timer2=&gt;promise2</code></li>
<li>如果是node10及其之前版本：要看第一个定时器执行完，第二个定时器是否在完成队列中。<ul>
<li>如果是第二个定时器还未在完成队列中，最后的结果为<code>timer1=&gt;promise1=&gt;timer2=&gt;promise2</code></li>
<li>如果是第二个定时器已经在完成队列中，则最后的结果为<code>timer1=&gt;timer2=&gt;promise1=&gt;promise2</code>(下文过程解释基于这种情况下)</li>
</ul>
</li>
</ul>
<p>1.全局脚本（main()）执行，将2个timer依次放入timer队列，main()执行完毕，调用栈空闲，任务队列开始执行；</p>
<p>2.首先进入timers阶段，执行timer1的回调函数，打印timer1，并将promise1.then回调放入microtask队列，同样的步骤执行timer2，打印timer2；</p>
<p>3.至此，timer阶段执行结束，event loop进入下一个阶段之前，执行microtask队列的所有任务，依次打印promise1、promise2</p>
<p>Node端的处理过程如下：</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/1/12/16841d5f85468047?imageslim" srcset="/blog/img/loading.gif" alt="img"></p>
<h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>浏览器和Node 环境下，microtask 任务队列的执行时机不同</p>
<ul>
<li>Node端，microtask 在事件循环的各个阶段之间执行</li>
<li>浏览器端，microtask 在事件循环的 macrotask 执行完之后执行</li>
</ul>

            </article>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                
              </div>
              
                <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！</p>
              
              
                <div class="post-prevnext row">
                  <div class="post-prev col-6">
                    
                    
                      <a href="/blog/2020/06/26/cookie%E5%92%8Ctoken/">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">cookie和token</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </div>
                  <div class="post-next col-6">
                    
                    
                      <a href="/blog/2020/06/26/%E4%BB%8B%E7%BB%8D%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81/">
                        <span class="hidden-mobile">介绍重绘和回流</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </div>
                </div>
              
            </div>

            
              <!-- Comments -->
              <div class="comments" id="comments">
                
                

              </div>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div id="tocbot"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    
  </main>

  
    <a id="scroll-top-button" href="#" role="button">
      <i class="iconfont icon-arrowup" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  

  

  <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
      <i class="iconfont icon-love"></i>
      <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener">
        <span>Fluid</span></a>
    </div>
    

    

    
  </div>
</footer>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="/blog/js/debouncer.js" ></script>
<script  src="/blog/js/main.js" ></script>

<!-- Plugins -->


  
    <script  src="/blog/js/lazyload.js" ></script>
  



  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/blog/js/clipboard-use.js" ></script>



  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





  <script  src="https://cdn.staticfile.org/tocbot/4.11.1/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var boardCtn = $('#board-ctn');
      var boardTop = boardCtn.offset().top;

      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: 'article.markdown-body',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        collapseDepth: 0,
        scrollSmooth: true,
        headingsOffset: -boardTop
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc').css('visibility', 'visible');
      }
    });
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "说说浏览器和Node事件循环的区别&nbsp;",
      ],
      cursorChar: "|",
      typeSpeed: 70,
      loop: true,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/blog/js/local-search.js" ></script>
  <script>
    var path = "/blog/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      searchFunc(path, 'local-search-input', 'local-search-result');
      this.onclick = null
    }
  </script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />

  <script>
    $('#post img:not(.no-zoom img, img[no-zoom]), img[zoom]').each(
      function () {
        var element = document.createElement('a');
        $(element).attr('data-fancybox', 'images');
        $(element).attr('href', $(this).attr('src'));
        $(this).wrap(element);
      }
    );
  </script>







  
  
    <script>
      !function (e, t, a) {
        function r() {
          for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999");
          requestAnimationFrame(r)
        }

        function n() {
          var t = "function" == typeof e.onclick && e.onclick;
          e.onclick = function (e) {
            t && t(), o(e)
          }
        }

        function o(e) {
          var a = t.createElement("div");
          a.className = "heart", s.push({
            el: a,
            x: e.clientX - 5,
            y: e.clientY - 5,
            scale: 1,
            alpha: 1,
            color: c()
          }), t.body.appendChild(a)
        }

        function i(e) {
          var a = t.createElement("style");
          a.type = "text/css";
          try {
            a.appendChild(t.createTextNode(e))
          } catch (t) {
            a.styleSheet.cssText = e
          }
          t.getElementsByTagName("head")[0].appendChild(a)
        }

        function c() {
          return "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + ")"
        }

        var s = [];
        e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (e) {
          setTimeout(e, 1e3 / 60)
        }, i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"), n(), r()
      }(window, document);
    </script>
  













</body>
</html>
